category: main
step: 5_full
sub_step:
doc_status: ready
language: rus
main_number: "08"macro full
| Name | Category | In Main Macro | Doc Status |
|---|---|---|---|
| clickhouse__check_table_exists | auxiliary | full | ready |
Макрос full предназначен для объединения данных пайплайна с registry-таблицами. В зависимости от пайплайна поведение макроса меняется.
Имя dbt-модели (=имя файла в формате sql в папке models) должно соответствовать шаблону:
full_{название_пайплайна}.
Например, full_events.
Внутри этого файла вызывается макрос:
{{ datacraft.full() }}
Над вызовом макроса в файле будет указана зависимость данных через —depends_on. То есть целиком содержимое файла выглядит, например, вот так:
-- depends_on: {{ ref('graph_qid') }}
-- depends_on: {{ ref('link_events') }}
-- depends_on: {{ ref('link_registry_appprofilematching') }}
-- depends_on: {{ ref('link_registry_utmhashregistry') }}
{{ datacraft.full() }}
Этот макрос принимает следующие аргументы:
params (по умолчанию: none)disable_incremental (по умолчанию: none)override_target_model_name (по умолчанию: none)date_from (по умолчанию: none)date_to (по умолчанию: none)limit0 (по умолчанию: none)metadata (по умолчанию: результат макроса project_metadata())Глобально работу макроса можно разделить на несколько смысловых этапов:
registry1) подготовка
Сначала макрос считает имя модели - либо из передаваемого аргумента (
override_target_model_name), либо из имени файла (this.name). При использовании аргумента override_target_model_name макрос работает так, как если бы находился в модели с именем, равным значению override_target_model_name.
Название модели, полученное тем или иным способом, разбивается на части по знаку нижнего подчёркивания. Например, название full_events разобьётся на 2 части, из этих частей макрос возьмёт в работу:
pipeline_name → events2) материализация
Для каждого пайплайна в макросе задаётся своя материализация и своё поведение в начале, до присоединения registry-таблиц:
для пайплайна events- материализация table и соединение данных из таблиц link_events + graph_qid + имеющихся таблиц пайплайна registry
для пайплайна datestat - материализация incremental и соединение данных из таблицы link_datestat + имеющихся таблиц пайплайна registry
для пайплайна periodstat - материализация incremental и разбиение метрик по дням + добавление имеющихся таблиц пайплайна registry
3) отбор возможных и существующих таблиц пайплайна registry
На этом этапе сначала в макросе создаётся список возможных таблиц пайплайна registry - это нужно для всех пайплайнов. Для создания такого списка макрос обращается к metadata (она передаётся через аргумент и по умолчанию metadata = результат макроса project_metadata()).
Далее макрос при помощи вспомогательного макроса clickhouse__check_table_exists будет отбирать те таблицы пайплайна registry, которые в действительности существуют.
4) создание основы запроса для каждого пайплайна
Для каждого пайплайна создаём основу будущего SQL-запроса. Оформляем это при помощи common table expressions - CTE. Этот основной CTE для всех пайплайнов будет называться одинаково: t0. К нему в дальнейшем будут добавляться t1, t2 и т.д. в зависимости от количества имеющихся таблиц пайплайна registry.
Для каждого пайплайна основа данных - своя. Поэтому макрос начинает перебор пайплайнов с помощью оператора if и каждому задаёт свою основу основу будущего запроса - t0:
events основа запроса это link_events + graph_qid:WITH t0 AS (
SELECT * FROM {{ ref('link_events') }}
LEFT JOIN {{ ref('graph_qid') }} USING (__id, __link, __datetime)
)
datestat это link_datestat:WITH t0 AS (
SELECT * FROM {{ ref('link_datestat') }}
)
periodstat поведение макроса более насыщенное: на этом этапе макрос берёт данные из link_periodstat и разбивает их по дням.Чтобы осуществить это поведение, внутри макроса понадобится произвести дополнительные действия.
В макросе задаются наименования числовых типов данных:
{%- set numeric_types = ['UInt8', 'UInt16', 'UInt32', 'UInt64', 'UInt256',
'Int8', 'Int16', 'Int32', 'Int64', 'Int128', 'Int256',
'Float8', 'Float16','Float32', 'Float64','Float128', 'Float256','Num'] -%}
Затем задаются два списка - для колонок с числовыми и нечисловыми типами данных.
Далее макрос при помощи вспомогательного макроса get_columns_in_relation берёт все колонки, проверяет у каждой тип данных, и распределяет их по этим двум спискам.
Для этого пайплайна макрос не сразу создаёт t0, а сначала делает подготовительный шаг - unnest_dates.
Здесь макрос разбивает период на дни. Например, в данных была одна строка с такими значениями:
Из этой одной строки макрос создаст 31 строку - по одной на каждый день этого периода и значением в новом столбце cost_per_day равным 1000. Вот как выглядит подготовительный шаг unnest_dates:
WITH unnest_dates AS (
SELECT *, {# берём все данные, какие были в таблице и добавляем к ним каждый день периода #}
dateAdd(periodStart, arrayJoin(range( 0, 1 + toUInt16(date_diff('day', periodStart, periodEnd))))) AS period_date
, COUNT(*) OVER(PARTITION BY
{% for c in columns_not_numeric -%}{{c}}
{% if not loop.last %},{% endif %}
{% endfor %}
) AS divide_by_days {# здесь мы вычисляем кол-во дней, на которое надо будет в дальнейшем делить метрики #}
FROM {{ ref('link_periodstat') }}
)
и после этого подготовительного шага уже создаётся t0. На этом шаге идёт отбор всех дат периода, нечисловые колонки идут в запрос SELECT в таком же виде, в каком они были изначально. А значения в числовых колонках делятся на количество дней в периоде. Таким образом для числовых данных образуются новые колонки, названия которых заканчиваются на _per_day. Например, была числовая колонка cost, а станет колонка cost_per_day. Вот как выглядит код этого шага:
, t0 AS (
SELECT period_date, {# отбираем все даты периода #}
{% for column in columns_not_numeric -%}{{column}}, {# не числовые колонки - такими какими они и были #}
{% endfor %} {# а значения в числовых колонках делим на количество дней в периоде #}
{% for column in columns_numeric -%}{{column}}/divide_by_days AS {{column}}_per_day {# и таким образом получаем новые столбцы #}
{% if not loop.last %},{% endif %} {# например вместо cost будет cost_per_day #}
{% endfor %}
FROM unnest_dates
)
5) отбор полей pipeline_columns для каждого пайплайна
На этом шаге нет условия if, но, поскольку при вызове моделей у каждой свой pipeline_name, значения будут разными.
Макрос задаёт переменную pipeline_columns, в которую будет отбирать колонки с сущностями каждого пайплайна.
Далее на этом шаге макрос обращается к metadata, отбирает линки и получает все необходимые для дальнейшей работы данные по каждому нужному линку.
Ранее созданная переменная pipeline_columns заполняется теми сущностями (entities) из метадаты, которые соответствуют пайплайну модели и её линкам. Каждое название сущности дополняется окончанием Hash, и таким образом в этой переменной в процессе работы макроса оказывается уникальный список захэшированных названий колонок сущностей.
Например, для пайплайна events такой список (pipeline_columns) может выглядеть следующим образом:
'AccountHash', 'AppMetricaDeviceHash', 'MobileAdsIdHash', 'CrmUserHash', 'OsNameHash', 'CityHash', 'AdSourceHash', 'UtmParamsHash', 'UtmHashHash', 'TransactionHash', 'PromoCodeHash', 'AppSessionHash', 'VisitHash', 'YmClientHash'
6) последовательное обогащение основного запроса данными из таблиц пайплайна registry
На этом шаге в макросе происходит цикл for для последовательных джойнов: основа запроса - t0- последовательно обогащается данными из таблиц пайплайна registry (автоматически создаются и добавляются t1, t2).
Чтобы джойны могли отработать, в макросе создаётся переменная fields_list. В неё будут отбираться поля для будущего USING(...) в блоке JOIN.
Макрос перебирает таблицы в ранее отобранном списке существующих таблиц пайплайна registry (см. шаг 3). В названии этих registry-таблиц последняя часть названия - это линк.
Макрос обращается на этом этапе к metadata. И для каждого линка из названия registry-таблиц макрос отбирает нужную для дальнейшей работы информацию об этом линке из metadata.
Далее в переменную fields_list добавляются названия отобранных для задействованных линков сущностей (entities) . К названиям сущностей добавляются окончания Hash (так же, как это происходило для наполнения переменной pipeline_columns).
После этого макрос создаёт переменную existing_fields_list. В неё отбираются только те значения из fields_list, которые есть в pipeline_columns.
Таким образом в existing_fields_list попадают те названия полей, которые не просто относятся к пайплайну (то есть являются возможными вариантами для пайплайна), но и есть в реально существующих таблицах.
Далее макрос проходит циклом:
t1, который обогащает t0 t2, который обогащает t1 и тд.Каждая таблица из t1, t2 и тд - это одна из таблиц пайплайна registry.
Для каждого оборота цикла в макросе автоматически подставляются и таблица registry, и её хэш-поля в USING(...) для верного джойна.
Джойн происходит до тех пор, пока у нас есть общие поля, по которым можно сделать USING(...).
Если общих полей для USING(...) нет, то мы этот шаг делаем без джойна, просто как SELECT * FROM предыдущий шаг.
После завершения цикла макрос обратится к последнему CTE и возьмёт все колонки кроме тех, которые не пойдут по условию с регулярным выражением. Регулярное выражение отсеет технически ненужные для дальнейшей работы колонки.
Если аргумент limit0 активирован, то в конце SQL-запроса будет добавлено LIMIT 0.
Файл в формате sql в папке models. Название файла full_events
Содержимое файла:
-- depends_on: {{ ref('graph_qid') }}
-- depends_on: {{ ref('link_events') }}
-- depends_on: {{ ref('link_registry_appprofilematching') }}
-- depends_on: {{ ref('link_registry_utmhashregistry') }}
{{ datacraft.full() }}
Это восьмой из основных макросов.